﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Collections.TreeCollections;
using System.Xml.Linq;
using System.IO;
using System.Collections;
using System.Threading;
using FeatureModel.Collections.TreeArgs;
using EventLogAssembly;

namespace Collections
{
    public delegate TreeObject ConstructNewInstance(TreeNodeIdentify id, object[] args);
    public delegate void TreeObjectXmlParse(XElement xelem, TreeObject obj);
    public delegate Tree ConstructTree(object[] args);
    public delegate Tree ConstructTreeWithRoot(TreeRoot root, object[] args);
    public delegate void TreeXmlParse(XElement xelem, Tree tree);


    public abstract class Tree : ICloneable
    {
        #region Xml Symbol
        private static readonly string _XML_Class = "Class";
        #endregion

        #region Event Handler
        /// <summary>
        /// Tree Modify Completed
        /// </summary>
        public TreeEventHandler TreeModifyCompleted { get; protected set; }
        public TreeEventHandler TreeModifyPrepare { get; protected set; }
        public TreeEventHandler TreeModifyCheck{ get; protected set; }
        #endregion

        #region Properties
        /// <summary>
        /// Tree Root
        /// </summary>
        public TreeRoot Root { get { return this._root; } }
        /// <summary>
        /// All Tree Object List
        /// </summary>
        public virtual TreeObject[] AllTreeObjects { get { return _objStorage.ToArray<TreeObject>(); } }
        public TreeObject this[TreeNodeIdentify index] { get { return this.getTreeObject(index); } }
        public TreeObject this[string index] { get { return getTreeObject(index); } }

        /// <summary>
        /// Full File Path
        /// </summary>
        public virtual bool HasPathInfo { get { return _hasFilePathInfo; } }
        public virtual string FullFilePath { get { return string.Format("{0}\\{1}"
            , _folderPath, _fileName);} }
        public virtual string FolderPath { get { return _folderPath; } }
        public virtual string FileName { get { return _fileName; } }
        public virtual bool Dirty { get { return _dirty; } }
        public virtual int Count { get { return _objStorage.Count; } }
        #endregion

        #region Fields
        /// <summary>
        /// Root
        /// </summary>
        protected TreeRoot _root;
        /// <summary>
        /// Name
        /// </summary>
        protected string _name;

        /// <summary>
        /// Folder Path
        /// </summary>
        protected bool _hasFilePathInfo;
        protected string _folderPath;
        protected string _fileName;
        protected bool _dirty;

        /// <summary>
        /// The list contains the object type information which this tree support.
        /// Key: Type
        /// Value: TreeObjectInfo
        /// </summary>
        protected TreeRegisterClass _treeRegister;
        /// <summary>
        /// Object Storage
        /// </summary>
        protected FMSet<TreeObject> _objStorage;
        #endregion

        #region Constructor
        /// <summary>
        /// Tree
        /// </summary>
        public Tree()
        {
            TreeModifyCompleted = new TreeEventHandler();
            TreeModifyPrepare  = new TreeEventHandler();
            TreeModifyCheck = new TreeEventHandler();

            _name = this.GetType().Name;

            // object Type List
            _treeRegister = TreeRegisterTable.TreeClassRegisterTable[this.GetType()];

            // store & load information
            _folderPath = string.Empty;
            _fileName = string.Empty;
            _dirty = false;

            // storage
            _objStorage = new FMSet<TreeObject>();
            // generate root
            _root = this.ConstructTreeRoot();
        }
        /// <summary>
        /// Construct Tree with Root
        /// </summary>
        /// <param name="root"></param>
        public Tree(TreeRoot root)
        {
            TreeModifyCompleted = new TreeEventHandler();
            TreeModifyPrepare = new TreeEventHandler();
            TreeModifyCheck = new TreeEventHandler();


            _name = this.GetType().Name;

            // object Type List
            _treeRegister = TreeRegisterTable.TreeClassRegisterTable[this.GetType()];

            // store & load information
            _folderPath = string.Empty;
            _fileName = string.Empty;
            _dirty = true;

            // add tree objects into storage
            _root = root;
            if (root != null)
            {
                _objStorage = getAllTreeObject(root);
                foreach (TreeObject obj in _objStorage)
                {
                    obj.TreeOwner = this;
                }
            }
            else
            {
                _objStorage = new FMSet<TreeObject>();
            }
        }
        #endregion

        /// <summary>
        /// Contains tree object
        /// </summary>
        /// <param name="treeObj"></param>
        /// <returns></returns>
        public bool Contains(TreeObject treeObj)
        {
            return _objStorage.Contains(treeObj);
        }

        #region Modify Tree
        /// <summary>
        /// Generate Identify
        /// </summary>
        /// <returns></returns>
        public TreeNodeIdentify GenerateId()
        {
            TreeNodeIdentify newid;
            // check whether there's duplicate
            while (true)
            {
                newid = new TreeNodeIdentify();
                bool flag = false;
                foreach (TreeObject obj in _objStorage)
                {
                    if (obj.ID.Equals(newid))
                    {
                        flag = true;
                        break;
                    }
                }
                if (flag == false)
                {
                    break;
                }
            }
            return newid;
        }
        /// <summary>
        /// Construct Root
        /// </summary>
        /// <returns></returns>
        protected virtual TreeRoot ConstructTreeRoot()
        {
            TreeNodeIdentify id = GenerateId();
            TreeObjectRegisterClass register = null;
            foreach (Type type in _treeRegister.Keys)
            {
                if (type.IsSubclassOf(typeof(TreeRoot)))
                {
                    register = _treeRegister[type];
                    break;
                }
            }
            TreeRoot root = (TreeRoot)register.Constructor(id, null);
            root.TreeOwner = this;
            this._objStorage.Add(root);
            return root;
        }
        /// <summary>
        /// Create new Instance
        /// </summary>
        public TreeObject CreateNewInstance(Type treeObjType, INonLeaf parent, params object[] args)
        {
            // generate id 
            TreeNodeIdentify id = GenerateId();

            StringBuilder strBuilder = new StringBuilder();
            if (args != null)
            {
                foreach (object obj in args)
                {
                    strBuilder.AppendFormat("{0}, ", obj);
                }
            }
            string info = string.Format("Create '{0}' object in '{1}' with parameters '{2}'"
                , treeObjType.GetType().Name
                , this.GetType().Name
                , strBuilder
                );
            TreeObjectCreatedEventArgs e = new TreeObjectCreatedEventArgs(this, info, 0
                , treeObjType, parent, args, id);

            this.PreProcess(this, e); // pre Prcess
            
            // Get Type
            TreeObjectRegisterClass objInfo = (TreeObjectRegisterClass)_treeRegister[treeObjType];
            TreeObject newInstance = objInfo.Constructor(id, args);
            INonRoot ni = newInstance as INonRoot;
            if (ni == null)
            {
                throw new Exception("Cannot Create new Tree Root Instance.");
            }
            // add to parent
            parent.AddChild(ni);
            // add to storage

            _objStorage.Add(newInstance);
            newInstance.TreeOwner = this;

            e.CreatedObject = (INonRoot)newInstance;
            this.ProProcess(this, e); // pro process
            return newInstance;
        }
        /// <summary>
        /// add existed Instance
        /// </summary>
        /// <param name="treeObjType"></param>
        /// <param name="treeObj"></param>
        public void AddInstance(INonLeaf parent, INonRoot treeObj)
        {
            string info = string.Format("Add instance '{0}' under parent '{1}' into tree '{2}'"
                , treeObj.GetType().Name
                , parent.GetType().Name
                , this.GetType().Name);
            TreeObjectAddedEventArgs e = new TreeObjectAddedEventArgs(this, info, 0
                , parent, treeObj);

            PreProcess(this, e);// preProcss

            if (this.Contains(((TreeObject)treeObj))
                || treeObj.Parent != null)
            {
                throw new Exception("The Tree Object has already had parent.");
            }
            if (this.TreeHierarchyValid())
            {
                return;
            }
            // add existed instance to tree
            addInstance(parent, treeObj);

            ProProcess(this, e);// pro process
        }
        /// <summary>
        /// add existed Instance
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="nonRoot"></param>
        protected virtual void addInstance(INonLeaf parent, INonRoot nonRoot)
        {
            parent.AddChild(nonRoot);
            // add to current tree storage
            TreeObject treeObj = (TreeObject)nonRoot;
            ((TreeObject)nonRoot).TreeOwner = this;
            this._objStorage.Add(treeObj);
        }
        /// <summary>
        /// Delete existed Instance from current Tree
        /// </summary>
        /// <param name="treeObjType"></param>
        public virtual void DeleteExistInstance(INonRoot treeObj)
        {
            if (!this.Contains((TreeObject)treeObj))
            {
                throw new Exception("The Tree Object is not in this tree.");
            }

            INonRoot[] dependantObjects = getDependantTreeObject(treeObj);
            string info = string.Format("Delete object '{0}' from tree '{1}'"
                , treeObj.GetType().Name
                , this.GetType().Name);
            TreeObjectDeletedEventArgs e = new TreeObjectDeletedEventArgs(this, info, 0
                , treeObj, dependantObjects);
            PreProcess(this, e); // pre Process


            if (!this.TreeHierarchyValid())
            {
                return;
            }

            // delete the tree objects
            removeTreeObjectFromTree((TreeObject)treeObj);
            if (treeObj.Parent != null)
            {
                treeObj.Parent.RemoveChild(treeObj);
            }
            foreach (INonRoot dObj in dependantObjects)
            {
                removeTreeObjectFromTree((TreeObject)dObj);
            }


            ProProcess(this, e);
        }
        /// <summary>
        /// Remove tree object from current tree
        /// </summary>
        /// <param name="treeObj"></param>
        protected void removeTreeObjectFromTree(TreeObject treeObj)
        {
            // remove from storage
            this._objStorage.Remove(treeObj);
            // remove form its parent
            INonRoot nonRoot = treeObj as INonRoot;
            return;
        }

        #endregion

        #region Pro & Pre Process
        /// <summary>
        /// Pro Process
        /// </summary>
        public void ProProcess(object sender, TreeEventArgs e)
        {
            if (TreeModifyCompleted != null)
            {
                TreeModifyCompleted.Invoke(sender, e);
            }
            this._dirty = true;
            FeatureModelEventLog.GetInstance().WriteInfo(e.EventInfo, System.Diagnostics.EventLogEntryType.Information, e.EventNum);
        }
        /// <summary>
        /// Pre Process
        /// </summary>
        public void PreProcess(object sender, TreeEventArgs e)
        {
            if (this.TreeModifyCheck != null)
            {
                TreeModifyCheck.Invoke(sender, e);
            }
            if (this.TreeModifyPrepare != null)
            {
                TreeModifyPrepare.Invoke(sender, e);
            }
        }
        #endregion

        #region XML Parser
        /// <summary>
        /// To xdocument
        /// </summary>
        /// <returns></returns>
        public virtual XDocument ToXDoc()
        {
            XDocument xdoc = new XDocument(this.ToXElem());
            return xdoc;
        }
        /// <summary>
        /// XDoc Parser
        /// </summary>
        /// <param name="type">Tree type</param>
        /// <param name="xdoc"></param>
        /// <returns></returns>
        public static Tree XDocParser(Type type, XDocument xdoc)
        {
            TreeRegisterClass treeRegister = TreeRegisterTable.TreeClassRegisterTable[type];
            XElement xmlRoot = xdoc.Root;

            return XElemParser(xmlRoot);
        }
        /// <summary>
        /// to Xelement
        /// </summary>
        /// <returns></returns>
        public virtual XElement ToXElem()
        {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
            XElement xelem = new XElement(_name);
            xelem.Add(_root.ToXElem());
            xelem.Add(new XAttribute(_XML_Class, this.GetType().FullName));

            return xelem;
        }
        /// <summary>
        /// Parse from xelement
        /// </summary>
        /// <param name="type"></param>
        /// <param name="xelem"></param>
        /// <returns></returns>
        public static Tree XElemParser(XElement xelem)
        {
            string name = xelem.Name.LocalName;
            string classStr = xelem.Attribute(_XML_Class).Value;

            TreeRegisterClass register = TreeRegisterTable.TreeClassRegisterTable[classStr];
            
            Type rootType = register.TreeRootRegister.TreeObjectType;
            XElement tmpXElem = xelem.Element(rootType.Name);
            TreeRoot tmpRoot = (TreeRoot)TreeObject.XmlParser(tmpXElem, register);
            TreeRoot.XmlParser(tmpXElem, register, tmpRoot);

            Tree tree = register.TreeConstructorRoot(tmpRoot, null);
            register.XmlParser(xelem, tree);

            string info = string.Format("Parse xml '{0}' to tree '{1}'"
                , xelem.ToString()
                , tree.GetType().Name);
            XmlParsedEventArgs e = new XmlParsedEventArgs(tree, info, 0);
            tree.ProProcess(tree, e);
            return tree;
        }
        #endregion

        /// <summary>
        /// Valid Tree Hierarchy
        /// </summary>
        /// <returns></returns>
        public virtual bool TreeHierarchyValid()
        {
            try
            {
                // get all node in tree hierarchy
                FMSet<TreeObject> allTreeObject = getAllTreeObject(this._root);
                if (!_objStorage.Equals(allTreeObject))
                {
                    return false;
                }

                // valid all object types have been registered.
                foreach (TreeObject treeObj in this._objStorage)
                {
                    bool flag = false;
                    Type type = treeObj.GetType();

                    // check whether type has been registered.
                    foreach (Type tmpType in _treeRegister.Keys)
                    {
                        if (tmpType == type)
                        {
                            flag = true;
                            break;
                        }
                    }
                    if (flag == false)
                    {
                        return false;
                    }
                }

                // check child to parent link
                foreach (TreeObject treeObj in _objStorage)
                {
                    INonRoot nonRoot = treeObj as INonRoot;
                    if (nonRoot == null)
                    {
                        continue;
                    }
                    bool flag = nonRoot.Parent.ContainChild(nonRoot);
                    if (flag == false)
                    {
                        return false;
                    }
                }

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #region Get Nodes
        /// <summary>
        /// Get all Tree Object in tree hierarchy
        /// </summary>
        /// <returns></returns>
        private static FMSet<TreeObject> getAllTreeObject(TreeRoot root)
        {
            int curObj = 0;
            List<TreeObject> set = new List<TreeObject>();
            set.Add(root);
            for (curObj = 0; curObj < set.Count; ++curObj)
            {
                INonLeaf teamObj = set[curObj] as INonLeaf;
                if (teamObj == null)
                {
                    continue;
                }
                foreach (INonRoot child in teamObj.Children)
                {
                   set.Add((TreeObject)child);
                }
            }

            return new FMSet<TreeObject>(set);
        }
        /// <summary>
        /// Get all tree objects from the node
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private static INonRoot[] getDependantTreeObject(INonRoot node)
        {
            int curObj = 0;
            List<INonRoot> set = new List<INonRoot>();
            set.Add(node);
            for (curObj = 0; curObj < set.Count; ++curObj)
            {
                INonLeaf teamObj = set[curObj] as INonLeaf;
                if (teamObj == null)
                {
                    continue;
                }
                foreach (INonRoot child in teamObj.Children)
                {
                    set.Add(child);
                }
            }

            return set.ToArray<INonRoot>();
        }
        /// <summary>
        /// Get Tree Object based on identify
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        private TreeObject getTreeObject(TreeNodeIdentify id)
        {
            foreach(TreeObject obj in _objStorage)
            {
                if(obj.ID.Equals(id))
                {
                    return obj;
                }
            }
            return null;
        }
        /// <summary>
        /// Get Tree Object based on identify string
        /// </summary>
        /// <param name="idStr"></param>
        /// <returns></returns>
        private TreeObject getTreeObject(string idStr)
        {
            TreeNodeIdentify id = new TreeNodeIdentify(idStr);
            return getTreeObject(id);
        }
        #endregion

        public abstract object Clone();

        #region Equals
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            Tree tree = obj as Tree;
            if (tree == null)
            {
                return false;
            }

            return this.TotallyEquals(tree);
        }
        /// <summary>
        /// Tree Hierarchy Equals
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        public virtual bool TreeHierarchyEquals(Tree tree)
        {
            // check valid
            bool flag = this.TreeHierarchyValid()
                && tree.TreeHierarchyValid();
            if (flag == false)
            {
                return false;
            }
            flag = this._objStorage.Equals(tree._objStorage);
            return flag;
        }
        /// <summary>
        /// Totally Equals
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        public virtual bool TotallyEquals(Tree tree)
        {
            bool flag = this.TreeHierarchyEquals(tree);
            if (flag == false)
            {
                return false;
            }
            flag = this._dirty == tree._dirty
                && this._fileName == tree._fileName
                && this._folderPath == tree._folderPath
                && this._hasFilePathInfo == tree._hasFilePathInfo
                && this._name == tree._name;

            return flag;
        }
        #endregion

        /// <summary>
        /// Set file information
        /// </summary>
        /// <param name="fileName"></param>
        public virtual void SetFileInfo(string fileName)
        {
            FileInfo fileInfo = new FileInfo(fileName);
            _folderPath = fileInfo.DirectoryName;
            _fileName = fileInfo.Name;
            _hasFilePathInfo = true;
        }

        /// <summary>
        /// Get the tree whose's root is the node
        /// </summary>
        /// <param name="getTreeFromNode"></param>
        /// <returns></returns>
        public Tree GetTree(TreeNode getTreeFromNode)
        {
            TreeRegisterClass registerClass = TreeRegisterTable.TreeClassRegisterTable[this.GetType()];
            if (registerClass == null)
            {
                return null;
            }

            throw new NotImplementedException();
        }
    }
}
